﻿namespace BattleCity.GameLogic

open Microsoft.Xna.Framework
open FMath
open UI
open Casanova
open System


 module Types = 

 //************* STATE *****************//

  type GameState = 
   {
     BlockGrid : Block[,]
     BulletList: Variable<List<Bullet>>
     EnemiesBulletList: Variable<List<Bullet * int>>
     PlayerTank : Variable<Tank>
     PlayerState : ref<PlayerState>
     PlayerTransitionClock : ref<float<s>>
     EnemiesList: Variable<List<Variable<Tank>>>
     SpawnedEnemiesList: Variable<List<Tank * float<s>>>
     BonusList : Variable<List<Bonus * Vector2<m> * float<s>>>
     SpawnedBonus : ref<List<Bonus * Vector2<m>>>
     PlayerLives : ref<int>
     DestroyedEnemies: ref<int>
     GeneratedEnemies: ref<int>
     ChangedBlockList: ref<List<int*int>>
     HittedEnemiesList : ref<List<int>>
     DestroyedEnemiesList: Variable<List<Tank * float<s>>>
     VariableCounter : ref<int>
     CompletedLevels : ref<int>
   }
 
 
  and  PlayerState = 
   | Spawning
   | Destroyed
   | Playing

//************* TANK ******************//
  and Tank =
   {
     Direction : Vector2<1>
     Position : Vector2<m>
     Velocity : float<m/s>
     WheelRollTheta: float<1>
     CollisionSquare : Square
     TankType : TankType
     IsStealth : bool
     Bonus : Bonus
     Weapon : WeaponType
   }

  and TankType =
   | Enemy of EnemyType
   | PlayerTank
   
  and EnemyType =
   | Blue
   | Silver
   | Black
  
  and WeaponType =
   | SingleBullet
   | DoubleBullet
   | RenforcedBullet

//************* BONUS *****************//
  and Bonus =
   | WeaponUp
   | Nuclear
   | Life
   | Shield
   | None

//************* BLOCK *****************//

  and BlockMakeUp =
   // Su questi due tipi di blocco uso i variable perchè possono essere distrutti dai proiettili
   | CeramicBrick of Variable<bool[,]> 
   | SteelBrick of Variable<bool[,]>
   // Questi tre invece non possono essere distrutti
   | Space
   | Stealth
   | Empty

  and Block =
   {
     makeUp : BlockMakeUp
     position : Vector2<m>
     collisionSquare : Option<Square>
   }

//************* BULLET *****************//

  and Bullet =
   {
    position : Variable<Vector2<m>>
    collisionSquare : Variable<Square>
    velocity : Variable<float<m/s>>
    direction : Vector2<1>
    intersection : Variable<(IntersectionType * IntersectionType)> //Projectiles can collide with up to two elements
    power : int
   }

 //************ COLLISION **************//

  //Bullet can intersect a block  
  and IntersectionType =
       | IntersectionWithBlock of int * int * float * float
       | NoCollision

 //Used to describe if a bullet can hit an external or an internal brick of a Block
  
  type BlockIntersection = 
   | ExternalRight 
   | Internal
   | ExternalLeft

 //Used to describe if: 
  
  type BulletBrickCollision = 
   | NoBrickFounded //there are no bricks in front of the bullet
   | NotYet //there is a brick in front of the bullet but there is no collision
   | Hitted //the bullet hit a brick

//************* MAP *****************//
  
  type LevelMap = BlockMakeUp [,]


//******************************************* TYPES UTILITY FUNCTION ***************************************//

  let createBlockMap (blockSize: float<m>) (map:LevelMap) =
   let lenght = sqrt(map.Length |> float)
   let offset = (lenght - 1.0) / 2.0
   let createBlockFromMap i j =
    let blockPosition = FMath.Vector2(((float)j - offset) * blockSize, ((float)i - offset) * blockSize)
    {
       makeUp = map.[i,j]
       position = blockPosition
       collisionSquare = Option.Some(Square(blockPosition, blockSize))
    }
   Array2D.init (lenght |> int) (lenght |> int) (fun i j -> createBlockFromMap i j)

  let createEmptyMap (blockSize: float<m>) (lenght: float) =
   let offset = (lenght - 1.0) / 2.0
   let createBlockFromMap i j =
    let blockPosition = FMath.Vector2(((float)j - offset) * blockSize, ((float)i - offset) * blockSize)
    {
       makeUp = BlockMakeUp.Empty
       position = blockPosition
       collisionSquare = Option.Some(Square(blockPosition, blockSize))
    }
   Array2D.init (lenght |> int) (lenght |> int) (fun i j -> createBlockFromMap i j)
    
 
  let getBricks block = 
   match block with
   | CeramicBrick x -> x;
   | SteelBrick x -> x;
   | _ -> Variable(fun () -> null)

  let getCurrentBricks block = 
   match block with
   | CeramicBrick x -> !x;
   | SteelBrick x -> !x;
   | _ -> null

  let getEnemyType etank =
   match etank with
    | Enemy x -> x
    | PlayerTank -> Blue

  let createEnemyType etype =
   Enemy(etype)
    